﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.IO;
using TSU.FAMC.ComputerEngineering.Classes.Circuits.Basis;
using TSU.FAMC.ComputerEngineering.Classes.Circuits.Events;

namespace TSU.FAMC.ComputerEngineering.Classes.Circuits
{
    /// <summary>
    /// Logical circuit.
    /// </summary>
    public class Circuit
    {
        // Events

        /// <summary>
        /// Fires when input signal arrives.
        /// </summary>
        public event SignalEventHandler OnInputSignal;

        /// <summary>
        /// Fires when output signal appears.
        /// </summary>
        public event SignalEventHandler OnOutputSignal;

        
        // Fields

        /// <summary>
        /// Indicates whether circuit has been already destroyed (preventing infinite recursion calls).
        /// </summary>
        protected bool IsDestroyed;

        // Properties


        private CircuitInterface _inputs;

        /// <summary>
        /// Circuit's input interface.
        /// </summary>
        public CircuitInterface Inputs
        {
            get { return _inputs; }
            set
            {
                _inputs = value;

                // registering event handlers
                RegisterInputsEventHandlers();
            }
        }



        private CircuitInterface _outputs;

        /// <summary>
        /// Circuit's output interface.
        /// </summary>
        public CircuitInterface Outputs
        {
            get { return _outputs; }
            set
            {
                _outputs = value;

                // registering event handlers
                RegisterOutputsEventHandlers();
            }
        }

        /// <summary>
        /// Storage for Elements.
        /// </summary>
        private List<Element> _elements;

        /// <summary>
        /// Circuit's element basis.
        /// </summary>
        public List<Element> Elements
        {
            get
            {
                if (_elements == null || (_elements.Count == 0 && (Outputs.Count + Inputs.Count) > 0))
                {
                    var roundedElements = new List<Element>();
                    foreach (var output in Outputs)
                    {
                        DoFindElements(output, roundedElements);
                    }

                    _elements = roundedElements;
                }

                return _elements;
            }
            private set { _elements = value; }
        }

        /// <summary>
        /// Searching for circuit elements by rounding circuit.
        /// </summary>
        /// <param name="currentElement">Current circuit element.</param>
        /// <param name="cache">Circuit elements cache.</param>
        private static void DoFindElements(Circuit currentElement, ICollection<Element> cache)
        {
            if (currentElement is Element)
            {
                // checking cache
                if (cache.Contains(currentElement))
                {
                    return;
                }

                // caching
                cache.Add((Element)currentElement);
            }
            
            // going down
            foreach (var input in currentElement.Inputs)
            {
                DoFindElements(input, cache);
            }
        }


        // Methods

        /// <summary>
        /// Default contructor.
        /// </summary>
        public Circuit()
        {
            // instantiating circuit interfaces.
            Inputs = new CircuitInterface();
            Outputs = new CircuitInterface();

            // instantiating elements storage
            Elements = new List<Element>();

            // registering event handlers
            RegisterEventHandlers();
        }

        /// <summary>
        /// Encapsulates event handler registration.
        /// </summary>
        private void RegisterEventHandlers()
        {
            // registering inputs event handlers
            RegisterInputsEventHandlers();

            // registering outputs event handlers
            RegisterOutputsEventHandlers();
        }

        /// <summary>
        /// Registers event handlers for inputs interface.
        /// </summary>
        private void RegisterInputsEventHandlers()
        {
            var onInputsModifiedDelegate = new CircuitInterfaceEventHandler(OnInputsModifiedHandler);
            Inputs.OnModified += onInputsModifiedDelegate;
            var onInputsPreModifiedDelegate = new CircuitInterfaceEventHandler(OnInputsPreModifiedHandler);
            Inputs.OnPreModified += onInputsPreModifiedDelegate;
        }

        /// <summary>
        /// Registers event handlers for outputs interface.
        /// </summary>
        private void RegisterOutputsEventHandlers()
        {
            var onOutputsModifiedDelegate = new CircuitInterfaceEventHandler(OnOutputsModifiedHandler);
            Outputs.OnModified += onOutputsModifiedDelegate;
            var onOutputsPreModifiedDelegate = new CircuitInterfaceEventHandler(OnOutputsPreModifiedHandler);
            Outputs.OnPreModified += onOutputsPreModifiedDelegate;
        }

        /// <summary>
        /// Fires OnInputSignal event.
        /// </summary>
        public void FireOnInputSignal(SignalEventArgs e)
        {
            if (ReferenceEquals(OnInputSignal, null))
            {
                return;
            }

            OnInputSignal(this, e);
        }

        /// <summary>
        /// Fires OnOutputSignal event.
        /// </summary>
        public void FireOnOutputSignal(SignalEventArgs e)
        {
            if (ReferenceEquals(OnOutputSignal, null))
            {
                return;
            }

            OnOutputSignal(this, e);
        }

        /// <summary>
        /// Accepts signal. Used for transmitting initial signals to the circuit.
        /// </summary>
        /// <param name="signal"></param>
        public void AcceptSignal(Signal signal)
        {
            var args = new SignalEventArgs {Signal = signal, Provider = this};
            FireOnInputSignal(args);
        }


        // Event handlers

        /// <summary>
        /// OnModified event handler for input interface.
        /// </summary>
        /// <param name="sender">Sender object (input interface).</param>
        /// <param name="e">Event arguments.</param>
        private void OnInputsModifiedHandler(object sender, CircuitInterfaceEventArgs e)
        {
            // registering event handlers for inputs

            // reregistering OnOutputSignal handlers for inputs
            var inputDelegate = new SignalEventHandler(InputOnOutputSignalHandler);

            foreach (var input in Inputs)
            {
                input.OnOutputSignal += inputDelegate;
            }            
        }

        /// <summary>
        /// OnPreModified event handler for input interface.
        /// </summary>
        /// <param name="sender">Sender object (input interface).</param>
        /// <param name="e">Event arguments.</param>
        private void OnInputsPreModifiedHandler(object sender, CircuitInterfaceEventArgs e)
        {
            // removing event handlers from inputs to create new on after modification

            var inputDelegate = new SignalEventHandler(InputOnOutputSignalHandler);

            foreach (var input in Inputs)
            {
                input.OnOutputSignal -= inputDelegate;
            }
        }

        /// <summary>
        /// OnModified event handler for output interface.
        /// </summary>
        /// <param name="sender">Sender object (input interface).</param>
        /// <param name="e">Event arguments.</param>
        private void OnOutputsModifiedHandler(object sender, CircuitInterfaceEventArgs e)
        {
            // registering event handlers for outputs

            // reregistering OnOutputSignal handlers for outputs
            var outputDelegate = new SignalEventHandler(OutputOnOutputSignalHandler);

            foreach (var output in Outputs)
            {
                output.OnOutputSignal += outputDelegate;
            }
        }

        /// <summary>
        /// OnPreModified event handler for output interface.
        /// </summary>
        /// <param name="sender">Sender object (input interface).</param>
        /// <param name="e">Event arguments.</param>
        private void OnOutputsPreModifiedHandler(object sender, CircuitInterfaceEventArgs e)
        {
            // removing event handlers for outputs to create new after modification

            // reregistering OnOutputSignal handlers for outputs
            var outputDelegate = new SignalEventHandler(OutputOnOutputSignalHandler);

            foreach (var output in Outputs)
            {
                output.OnOutputSignal -= outputDelegate;
            }
        }

        /// <summary>
        /// OnOutputSignal event handler for input interface.
        /// </summary>
        /// <param name="sender">Sender object (input circuit).</param>
        /// <param name="e">Event arguments.</param>
        private void InputOnOutputSignalHandler(object sender, SignalEventArgs e)
        {
            // checking whether provider belongs to inputs
            if (Inputs.IndexOf(e.Provider) < 0)
            {
                throw new InvalidDataException("Signal which has been detected as an input must belongs to circuit input interface.");
            }

            // firing OnInputSignal event
            if (ReferenceEquals(e.Provider, this))
            {
                // cycle
                return;
            }

            FireOnInputSignal(e);
        }

        /// <summary>
        /// OnInputSignal event handler for output interface.
        /// </summary>
        /// <param name="sender">Sender object (output circuit).</param>
        /// <param name="e">Event arguments.</param>
        private void OutputOnOutputSignalHandler(object sender, SignalEventArgs e)
        {
            // firing OnOutputSignal event
            if (ReferenceEquals(e.Provider, this))
            {
                // cycle
                return;
            }

            if (sender is Circuit 
                && Outputs.IndexOf((Circuit)sender) >= 0 
                && ((Circuit)sender).Inputs.IndexOf(this) >= 0)
            {
                // backward propagation
                return;
            }

            FireOnOutputSignal(e);
        }

        /// <summary>
        /// Counts circuit paths from outputs to inputs.
        /// </summary>
        /// <returns>Number of paths in the circuit.</returns>
        public int CountPaths()
        {
            return Inputs.Sum(input => DoCountPaths(input));
        }

        /// <summary>
        /// Performs path counting from inputs to outputs.
        /// </summary>
        /// <param name="currentElement">Current circuit element.</param>
        /// <param name="currentPath">Current explored path.</param>
        /// <returns>Number of paths relative current element.</returns>
        private int DoCountPaths(Circuit currentElement, List<Circuit> currentPath = null)
        {
            if (currentPath == null)
            {
                // new input
                currentPath = new List<Circuit>();
            }

            if (currentPath.Contains(currentElement))
            {
                // back link
                return 0;
            }

            // updating path
            currentPath = new List<Circuit>(currentPath) {currentElement};

            // checking wehther output reached
            if (currentElement.Outputs.Count == 0)
            {
                if (Outputs.IndexOf(currentElement) >= 0)
                {
                    // circuit output found
                    return 1;
                }
                
                // breaked circuit line
                //throw new Exception("Breaked circuit line!");
            }

            var result = currentElement.Outputs.Sum(output => DoCountPaths(output, currentPath));
            return result;
        }

        /// <summary>
        /// Counts gates in circuit.
        /// </summary>
        /// <returns>Number of gates in circuit.</returns>
        public int CountGates()
        {
            return Elements.Where(el => (el is Gate) && !(el is StubGate) && !(el is FlipFlop)).Count();
        }

        /// <summary>
        /// Resets state of the circuit and all it's elements.
        /// </summary>
        public virtual void Reset()
        {
            foreach (var output in Outputs)
            {
                DoReset(output);
            }
        }

        /// <summary>
        /// Resets all circuit elements by rounding circuit.
        /// </summary>
        /// <param name="currentElement">Current circuit element.</param>
        private static void DoReset(Circuit currentElement)
        {
            currentElement.Reset();

            // going deeper
            foreach (var input in currentElement.Inputs)
            {
                DoReset(input as Element);
            }
        }

        /// <summary>
        /// Clones circuit.
        /// </summary>
        /// <returns>Identical Circuit</returns>
        public virtual object Clone()
        {
            var clone = new Circuit();
            var elementsCache = new Dictionary<Circuit, Circuit>();
            
            // cloning from outputs
            /*
            foreach (var output in Outputs)
            {
                DoCloneFromOutputs(output, elementsCache);
            }
             * */

            // cloning from inputs
            foreach (var input in Inputs)
            {
                DoCloneFromInputs(input, elementsCache);
            }

            // setting outputs and inputs
            foreach (var clonedOutput in Outputs.Select(output => elementsCache.ContainsKey(output) ? elementsCache[output] : null).Where(clonedOutput => clonedOutput != null))
            {
                clone.Outputs.Add(clonedOutput);
            }

            foreach (var clonedInput in Inputs.Select(input => elementsCache.ContainsKey(input) ? elementsCache[input] : null).Where(clonedInput => clonedInput != null))
            {
                clone.Inputs.Add(clonedInput);
            }

            return clone;
        }

        /// <summary>
        /// Clones circuit by rounding it recursively from inputs.
        /// </summary>
        /// <param name="currentElement">Currently rounding circuit element.</param>
        /// <param name="elementsCache">Already created elements.</param>
        private static Circuit DoCloneFromInputs(Circuit currentElement, IDictionary<Circuit, Circuit> elementsCache)
        {
            // checking cache
            var search = elementsCache.Where(e => ReferenceEquals(e.Key, currentElement)).ToList();
            if (search.Count > 0)
            {
                return search.First().Value;
            }

            // cloning element
            Circuit clonedElement = null;
            if (currentElement is Link)
            {
                clonedElement = (Link)currentElement.Clone();
            }
            else if (currentElement is Joint)
            {
                clonedElement = (Joint)currentElement.Clone();
            }
            else if (currentElement is AndGate)
            {
                clonedElement = (AndGate)currentElement.Clone();
            }
            else if (currentElement is NAndGate)
            {
                clonedElement = (NAndGate)currentElement.Clone();
            }
            else if (currentElement is OrGate)
            {
                clonedElement = (OrGate)currentElement.Clone();
            }
            else if (currentElement is NOrGate)
            {
                clonedElement = (NOrGate)currentElement.Clone();
            }
            else if (currentElement is Constant0Gate)
            {
                clonedElement = (Constant0Gate)currentElement.Clone();
            }
            else if (currentElement is Constant1Gate)
            {
                clonedElement = (Constant1Gate)currentElement.Clone();
            }
            else if (currentElement is NotGate)
            {
                clonedElement = (NotGate)currentElement.Clone();
            }
            else if (currentElement is StubGate)
            {
                clonedElement = (StubGate)currentElement.Clone();
            }
            else if (currentElement is Dff)
            {
                clonedElement = (Dff)currentElement.Clone();
            }

            if (clonedElement == null)
            {
                throw new Exception("Could not clone circuit element.");
            }

            clonedElement.Outputs.Clear();
            clonedElement.Inputs.Clear();

            // caching
            elementsCache.Add(currentElement, clonedElement);


            // going up
            foreach (var clonedParent in currentElement.Outputs.Select(parent => DoCloneFromInputs(parent, elementsCache)))
            {
                clonedElement.Outputs.Add(clonedParent);
                clonedParent.Inputs.Add(clonedElement);
            }

            return clonedElement;
        }

        /// <summary>
        /// Destroys ciruict.
        /// </summary>
        public virtual void Destroy()
        {
            if (IsDestroyed)
            {
                // preventing infinite recursion
                return;
            }

            IsDestroyed = true;

            // destroing outputs
            foreach (var ouput in Outputs)
            {
                ouput.Destroy();
            }

            // destroying inputs
            foreach (var input in Inputs)
            {
                input.Destroy();
            }

            // removing references
            Outputs.Clear();
            Inputs.Clear();
        }
        
        /// <summary>
        /// Extracts combinational equivalent from circuit by breaking back links.
        /// </summary>
        /// <returns>Combinational part of logical circuit.</returns>
        public Circuit ExtractCombinationalEquivalent()
        {
            // cloning circuit
            var clone = (Circuit) Clone();

            // searching for flip flops
            var flipFlops = clone.Elements.Where(el => el is FlipFlop).ToList();
            foreach (var flipFlop in flipFlops)
            {
                // breaking link

                // new output
                var outputPin = flipFlop.Inputs[0];
                var output = new Link { Name = flipFlop.Name };
                outputPin.Outputs.Remove(flipFlop);
                outputPin.Outputs.Add(output);
                output.Inputs.Add(outputPin);
                clone.Outputs.Add(output);

                // new input
                var inputPin = flipFlop.Outputs[0];
                var input = new StubGate {Name = flipFlop.Name};
                inputPin.Inputs.Remove(flipFlop);
                inputPin.Inputs.Add(input);
                input.Outputs.Add(inputPin);
                clone.Inputs.Add(input);

                // TODO: if flip-flop is output
                if (clone.Outputs.Contains(flipFlop))
                {
                    // removing it
                    clone.Outputs.Remove(flipFlop);
                }

                // updating elements
                clone.Elements.Remove(flipFlop);
                clone.Elements.Add(output);
                clone.Elements.Add(input);
            }

            // performing some checking
            if ((Inputs.Count + flipFlops.Count != clone.Inputs.Count) ||
                (Outputs.Count + flipFlops.Count != clone.Outputs.Count))
            {
                throw new Exception(
                    "Circuit inputs and outputs number should increase proportional number of flip flops!");
            }

            return clone;
        }
    }
}
